package com.fm.base.struct;

import com.fm.base.annotation.UnThreadSafe;
import com.fm.base.util.Pair;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

@UnThreadSafe
public class BasicHashMap<K, V> extends HashTable<K> implements Map<K, V>, Iterable<Pair<K, V>> {
	protected static final float DEFAULT_LOAD_FACTOR = 0.75F;
	protected static final float DEFAULT_EXPAND_FACTOR = 1F;

	private static class BasicHashNode<K, V> extends Pair<K, V> implements HashNode<K> {
		private BasicHashNode<K, V> next;

		private BasicHashNode(K key, V value) {
			super(key, value);
		}

		public K getKey() {
			return left;
		}

		public V getValue() {
			return right;
		}

		@Override
		public K getElement() {
			return left;
		}

		@Override
		public HashNode<K> getNext() {
			return next;
		}

		@Override
		public void setNext(HashNode<K> next) {
			this.next = (BasicHashNode<K, V>) next;
		}
	}

	public BasicHashMap() {
		this(1, DEFAULT_LOAD_FACTOR, DEFAULT_EXPAND_FACTOR);
	}

	public BasicHashMap(int initialCapacity) {
		this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_EXPAND_FACTOR);
	}

	public BasicHashMap(int initialCapacity, float loadFactor) {
		this(initialCapacity, loadFactor, DEFAULT_EXPAND_FACTOR);
	}

	@SuppressWarnings({"unchecked"})
	public BasicHashMap(int initialCapacity, float loadFactor, float expandFactor) {
		if (initialCapacity < 0) {
			throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
		} else if (initialCapacity == 0) {
			initialCapacity = 1;
		}
		if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
			throw new IllegalArgumentException("Illegal loadFactor: " + loadFactor);
		} else {
			this.loadFactor = loadFactor;
		}
		if (expandFactor <= 0 || Float.isNaN(expandFactor)) {
			throw new IllegalArgumentException("Illegal expandFactor: " + expandFactor);
		} else {
			this.expandFactor = expandFactor;
		}
		table = (BasicHashNode<K, V>[]) new BasicHashNode[initialCapacity];
		threshold = (int) Math.min(initialCapacity * loadFactor, Integer.MAX_VALUE);
	}

	@Override
	protected int hash(K element) {
		return element.hashCode();
	}

	@SuppressWarnings("unchecked")
	protected final void reHash() {
		if (table.length >= Integer.MAX_VALUE) {
			return;
		}
		int newCapacity = (int) Math.ceil(table.length * (1 + expandFactor));
		if (newCapacity <= 0) {
			newCapacity = Integer.MAX_VALUE;
			threshold = Integer.MAX_VALUE;
		} else {
			threshold = (int) (newCapacity * loadFactor);
		}
		BasicHashNode<K, V>[] newTable = (BasicHashNode<K, V>[]) new BasicHashNode[newCapacity];
		for (HashNode<K> row : table) {
			BasicHashNode<K, V> node = (BasicHashNode<K, V>) row;
			while (node != null) {
				int i = render(node.left.hashCode(), newTable.length);
				BasicHashNode<K, V> next = node.next;
				node.next = newTable[i];
				newTable[i] = node;
				node = next;
			}
		}
		table = newTable;
	}

	@Override
	public int size() {
		return size;
	}

	@Override
	public boolean isEmpty() {
		return size == 0;
	}

	@Override
	public boolean containsKey(Object key) {
		return false;
	}

	@Override
	public boolean containsValue(Object value) {
		return false;
	}

	@Override
	@SuppressWarnings("unchecked")
	public V get(Object key) {
		BasicHashNode<K, V> node = (BasicHashNode<K, V>) getNode((K) key);
		return node == null ? null : node.right;
	}

	@Override
	public V put(K key, V value) {
		int hash = key.hashCode();
		int index;
		BasicHashNode<K, V> node = (BasicHashNode<K, V>) table[index = render(hash, table.length)];
		if (node == null) {
			table[index] = new BasicHashNode<>(key, value);
		} else {
			while (true) {
				if (node.left.equals(key)) {
					V oldValue = node.right;
					node.right = value;
					return oldValue;
				}
				if (node.next != null) {
					node = node.next;
				} else {
					break;
				}
			}
			node.next = new BasicHashNode<>(key, value);
		}
		if (++size > threshold) {
			reHash();
		}
		return null;
	}

	@Override
	public void putAll(Map<? extends K, ? extends V> m) {
		for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
			put(entry.getKey(), entry.getValue());
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	public V remove(Object key) {
		BasicHashNode<K, V> entry = (BasicHashNode<K, V>) removeNode((K) key);
		return entry == null ? null : entry.right;
	}

	public Iterator<Pair<K, V>> iterator() {
		return new Iterator<Pair<K, V>>() {
			boolean needLoop = true;
			int index = -1;
			BasicHashNode<K, V> pre = null;
			BasicHashNode<K, V> cur = null;

			@Override
			public boolean hasNext() {
				if (needLoop) {
					if (cur != null && cur.next != null) {
						pre = cur;
						cur = cur.next;
						needLoop = false;
						return true;
					} else {
						pre = null;
						for (index += 1; index < table.length; ++index) {
							if (table[index] != null) {
								cur = (BasicHashNode<K, V>) table[index];
								needLoop = false;
								return true;
							}
						}
					}
				} else {
					return cur != null;
				}
				return false;
			}

			@Override
			public Pair<K, V> next() {
				needLoop = true;
				return cur;
			}

			@Override
			public void remove() {
				if (cur != null) {
					if (pre != null) {
						pre.next = cur.next;
					} else {
						table[index] = cur.next;
					}
				}
			}
		};
	}

	@Override
	public void clear() {
		for (int i = 0; i < table.length;) {
			table[i++] = null;
		}
		size = 0;
	}

	@Override
	public Set<K> keySet() {
		throw new UnsupportedOperationException();
	}

	@Override
	public Collection<V> values() {
		throw new UnsupportedOperationException();
	}

	@Override
	public Set<Entry<K, V>> entrySet() {
		return new Set<Entry<K, V>>() {
			@Override
			public int size() {
				return BasicHashMap.this.size;
			}

			@Override
			public boolean isEmpty() {
				return BasicHashMap.this.size == 0;
			}

			@Override
			public boolean contains(Object o) {
				return BasicHashMap.this.containsKey(o);
			}

			@Override
			public Iterator<Entry<K, V>> iterator() {
				return new Iterator<Entry<K, V>>() {
					Iterator<Pair<K, V>> iterator = BasicHashMap.this.iterator();

					@Override
					public boolean hasNext() {
						return iterator.hasNext();
					}

					@Override
					public Entry<K, V> next() {
						return new Entry<K, V>() {
							Pair<K, V> pair = iterator.next();

							@Override
							public K getKey() {
								return pair.left;
							}

							@Override
							public V getValue() {
								return pair.right;
							}

							@Override
							public V setValue(V value) {
								return pair.right = value;
							}

							@Override
							public String toString() {
								return pair.toString();
							}

							@Override
							public boolean equals(Object obj) {
								if (obj == this) {
									return true;
								}
								if (obj instanceof Map.Entry) {
									Map.Entry<?, ?> e = (Map.Entry<?, ?>) obj;
									if (Objects.equals(pair.left, e.getKey()) && Objects.equals(pair.right, e.getValue()))
										return true;
								}
								return false;
							}

							@Override
							public int hashCode() {
								return Objects.hashCode(pair.left) ^ Objects.hashCode(pair.right);
							}
						};
					}
				};
			}

			@Override
			public Object[] toArray() {
				throw new UnsupportedOperationException();
			}

			@Override
			public <T> T[] toArray(T[] a) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean add(Entry<K, V> kvEntry) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean remove(Object o) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean containsAll(Collection<?> c) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean addAll(Collection<? extends Entry<K, V>> c) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean retainAll(Collection<?> c) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean removeAll(Collection<?> c) {
				throw new UnsupportedOperationException();
			}

			@Override
			public void clear() {
				throw new UnsupportedOperationException();
			}
		};
	}
}
